/**
// 这个结构体是 Linux 内核块设备多队列（blk-mq）架构的核心组件，用于管理标签（请求）分配和硬件队列映射。
// 初始化tag_set
struct blk_mq_tag_set *set = kzalloc(sizeof(*set), GFP_KERNEL);
set->ops = &my_driver_mq_ops;
set->nr_hw_queues = nr_cpu_ids;
set->queue_depth = 128;
set->reserved_tags = 16;
set->cmd_size = sizeof(struct my_driver_cmd);
set->numa_node = NUMA_NO_NODE;
set->flags = BLK_MQ_F_SHOULD_MERGE;

// 设置队列映射
for (i = 0; i < set->nr_hw_queues; i++)
    set->map[HCTX_TYPE_DEFAULT].mq_map[i] = i % set->nr_hw_queues;
set->nr_maps = 1;

// 分配标签
blk_mq_alloc_tag_set(set);
*/

struct blk_mq_queue_map {
	// CPU ID 到硬件队列索引的映射数组
	// 作用：这是一个大小为 nr_cpu_ids 的数组，每个元素对应一个 CPU 核心
	// 索引：数组下标是 CPU ID（0, 1, 2, ..., nr_cpu_ids-1）
	// 值：每个元素的值表示该 CPU 应该使用哪个硬件队列
	// 取值范围：[queue_offset, queue_offset + nr_queues - 1]
	// // 假设系统有4个CPU，nr_queues=2，queue_offset=0
	// mq_map[0] = 0  // CPU0 使用硬件队列0
	// mq_map[1] = 1  // CPU1 使用硬件队列1  
	// mq_map[2] = 0  // CPU2 使用硬件队列0
	// mq_map[3] = 1  // CPU3 使用硬件队列1
	unsigned int *mq_map;
	unsigned int nr_queues;  // 用于映射CPU IDs的硬件队列数量
	unsigned int queue_offset;  // 硬件队列的起始偏移量
	// queue_offset <= value < queue_offset + nr_queues
};


struct blk_mq_tag_set {
	const struct blk_mq_ops	*ops;

    // 定义软件队列（ctx）到硬件队列（hctx）的映射关系。支持多种映射类型（默认、读、轮询）。
	struct blk_mq_queue_map	map[HCTX_MAX_TYPES];

    // 指定实际使用的映射类型数量，范围 [1, HCTX_MAX_TYPES]。
	unsigned int		nr_maps;

    // 块设备硬件支持的队列数量，通常对应CPU核心数或设备硬件队列数。
	unsigned int		nr_hw_queues;

    // 每个硬件队列支持的最大未完成请求数（包含保留标签），也就是每个硬件队列的总标签数。
	unsigned int		queue_depth;

    // 为高优先级请求（BLK_MQ_REQ_RESERVED）保留的标签数量，防止普通请求饿死高优先级请求。
	unsigned int		reserved_tags;

    // 为每个请求分配额外内存空间，供驱动存储私有数据。
	unsigned int		cmd_size;

    // 指定设备连接的NUMA节点，用于优化内存访问性能。
	int			numa_node;

    // 请求处理超时时间（以jiffies为单位）。
	unsigned int		timeout;

    // 控制标志
    // BLK_MQ_F_SHOULD_MERGE：允许请求合并
    // BLK_MQ_F_BLOCKING：支持阻塞队列
    // BLK_MQ_F_NO_SCHED：禁用I/O调度器
	unsigned int		flags;

    // 驱动可以存储任意私有数据的指针。
	void			*driver_data;

    // 每个硬件队列对应的标签集合，用于请求分配和追踪。
	struct blk_mq_tags	**tags;

    // 所有硬件队列共享的标签集合（如果设置），用于某些特定场景。
	struct blk_mq_tags	*shared_tags;
	struct mutex		tag_list_lock;
	struct list_head	tag_list;
	struct srcu_struct	*srcu;
};

/*
// 这个结构体高效地管理了标签到请求的映射关系，通过位图机制实现了快速的标签分配和释放，是blk-mq高性能IO处理的关键组件。
// 创建标签集合
struct blk_mq_tags *tags = blk_mq_init_tags(nr_tags, reserved_tags, node);

// 初始化位图
sbitmap_queue_init_node(&tags->bitmap_tags, nr_tags - reserved_tags, -1, false, node);
sbitmap_queue_init_node(&tags->breserved_tags, reserved_tags, -1, false, node);

// 分配请求数组
tags->rqs = kcalloc_node(nr_tags, sizeof(struct request*), GFP_KERNEL, node);

// 请求分配过程
// 1. 分配标签
unsigned int tag = blk_mq_get_tag(tags, reserved);

// 2. 获取或创建请求
struct request *rq = tags->rqs[tag];
if (!rq) {
    rq = blk_mq_alloc_request(..., tag);
    tags->rqs[tag] = rq;
}

// 3. 使用完成后释放标签
blk_mq_put_tag(tags, tag);
*/
struct blk_mq_tags {
    // 该标签集合管理的总标签数量（包含保留标签）。
    // 总标签数 = queue_depth = nr_tags
    // 普通标签数 = queue_depth - reserved_tags
    // 保留标签数 = reserved_tags
	unsigned int nr_tags;

    // 为高优先级请求保留的标签数量，这些标签只能用于 BLK_MQ_REQ_RESERVED 类型的请求。
	unsigned int nr_reserved_tags;

    // 当前使用此标签集合的活动硬件队列数量，用于引用计数和资源管理。
	unsigned int active_queues;

    // 管理普通标签的分配和释放，使用位图来跟踪哪些标签当前被使用。
    // 使用 sbitmap（稀疏位图）高效管理大量标签
    // 支持多线程并发分配
    // 提供标签的分配、释放和查找功能
	struct sbitmap_queue bitmap_tags;

    // 专门管理保留标签的分配和释放，与普通标签隔离。
	struct sbitmap_queue breserved_tags;

    // 标签到请求对象的映射数组。通过标签号可以快速找到对应的请求对象。
    // 索引 0 到 nr_reserved_tags-1：保留标签对应的请求
    // 索引 nr_reserved_tags 到 nr_tags-1：普通标签对应的请求
	struct request **rqs;

    // 指向静态分配的请求对象数组，在标签集合初始化时预先分配。
	struct request **static_rqs;
	struct list_head page_list;
	spinlock_t lock;
};

/*
// 软件队列

// 请求提交过程
1. 获取当前CPU的软件队列
struct blk_mq_ctx *ctx = blk_mq_get_ctx(q);
2. 根据请求类型选择硬件队列类型
enum hctx_type type = HCTX_TYPE_DEFAULT;
3. 获取对应的硬件队列
struct blk_mq_hw_ctx *hctx = ctx->hctxs[type];
4. 将请求添加到软件队列的对应链表
spin_lock(&ctx->lock);
list_add_tail(&rq->queuelist, &ctx->rq_lists[type]);
spin_unlock(&ctx->lock);

软件队列与硬件队列的映射关系
CPU 0: blk_mq_ctx[0] ──────→ hctx0 (默认), hctx1 (读)
CPU 1: blk_mq_ctx[1] ──────→ hctx1 (默认), hctx0 (读)  
CPU 2: blk_mq_ctx[2] ──────→ hctx0 (默认), hctx1 (读)
CPU 3: blk_mq_ctx[3] ──────→ hctx1 (默认), hctx0 (读)
*/
struct blk_mq_ctx {
	struct {
		spinlock_t		lock;

        // 用途：按硬件队列类型分类的待处理请求链表，索引类型：
        // HCTX_TYPE_DEFAULT：默认IO请求
        // HCTX_TYPE_READ：读请求（可单独配置）
        // HCTX_TYPE_POLL：轮询模式请求
		struct list_head	rq_lists[HCTX_MAX_TYPES];
	};

    // 标识该软件队列绑定的 CPU 核心编号
	unsigned int		cpu;

    // 记录每种硬件队列类型对应的硬件队列索引，如：
    // ctx->index_hw[HCTX_TYPE_DEFAULT] = 2;  // 默认类型使用硬件队列2
    // ctx->index_hw[HCTX_TYPE_READ] = 1;     // 读类型使用硬件队列1
	unsigned short		index_hw[HCTX_MAX_TYPES];

    // 指向对应硬件队列的指针数组，提供快速访问，关系如下
    // hctxs[i] = &queue->queue_hw_ctx[index_hw[i]]
	struct blk_mq_hw_ctx 	*hctxs[HCTX_MAX_TYPES];

    // 指向该软件队列所属的块设备请求队列
	struct request_queue	*queue;

    // 指向包含所有软件队列上下文的结构体（用于多队列管理）
	struct blk_mq_ctxs      *ctxs;
	struct kobject		kobj;
};


// 这个结构体 blk_mq_hw_ctx 是 Linux 内核块设备多队列架构中的硬件队列上下文，代表一个直接面向硬件设备的队列。

struct blk_mq_hw_ctx {
    // dispatch 列表：存储已准备好但暂时无法发送到硬件的请求（如资源不足时）
	struct {
		spinlock_t		lock;
		struct list_head	dispatch;
		unsigned long		state;
	};
    // 用于延迟运行硬件队列的处理
	struct delayed_work	run_work;

    // 定义该硬件队列可以在哪些CPU核心上运行
	cpumask_var_t		cpumask;

    // 实现轮询方式的CPU选择，均衡负载
	int			next_cpu;
	int			next_cpu_batch;

	unsigned long		flags;
    // I/O调度器可以存储任意私有数据
	void			*sched_data;
    // 指向拥有此硬件队列的请求队列
	struct request_queue	*queue;
	struct blk_flush_queue	*fq;

    // 硬件队列关联的驱动私有数据
	void			*driver_data;
    
    // 位图跟踪哪些软件队列有待处理请求
	struct sbitmap		ctx_map;
    // 无调度器时使用的软件队列
	struct blk_mq_ctx	*dispatch_from;
    // 使用指数加权移动平均算法判断队列是否繁忙
	unsigned int		dispatch_busy;

    // 标识硬件队列的类型（默认、读、轮询）
	unsigned short		type;

    // 关联的软件队列数量
	unsigned short		nr_ctx;
    // 关联的软件队列数组
	struct blk_mq_ctx	**ctxs;
	spinlock_t		dispatch_wait_lock;
	wait_queue_entry_t	dispatch_wait;
	atomic_t		wait_index;

    // 块设备驱动拥有的标签集合，请求分派时分配
	struct blk_mq_tags	*tags;

    // I/O调度器拥有的标签集合，请求分配时使用
	struct blk_mq_tags	*sched_tags;
    // 已分派请求的数量统计
	unsigned long		run;
	unsigned int		numa_node;

    // 该硬件队列在队列集合中的索引号
	unsigned int		queue_num;
	atomic_t		nr_active;
	struct hlist_node	cpuhp_online;
	struct hlist_node	cpuhp_dead;
	struct kobject		kobj;
	struct list_head	hctx_list;
};

队列总数等于：
nr_io_queues = 
min(opts->nr_io_queues, num_online_cpus()) +
min(opts->nr_write_queues, num_online_cpus()) +
min(opts->nr_poll_queues, num_online_cpus());

实际上创建的队列总数等于：
nr_queuees + 1

if (opts->nr_write_queues && opts->nr_io_queues < nr_io_queues) {
    // 读队列数等于IO队列数
    io_queues[HCTX_TYPE_READ] = opts->nr_io_queues;
    nr_io_queues -= io_queues[HCTX_TYPE_READ];
    // 默认队列数等于总队列数减去读IO队列数量与写IO队列数的最小值
    io_queues[HCTX_TYPE_DEFAULT] = min(opts->nr_write_queues, nr_io_queues);
    nr_io_queues -= io_queues[HCTX_TYPE_DEFAULT];
} else {
    io_queues[HCTX_TYPE_DEFAULT] = min(opts->nr_io_queues, nr_io_queues);
    nr_io_queues -= io_queues[HCTX_TYPE_DEFAULT];
}

if (opts->nr_poll_queues && nr_io_queues) {
    // 轮询队列数等于剩下的队列数和设定的轮训队列数量的最小值
    io_queues[HCTX_TYPE_POLL] = min(opts->nr_poll_queues, nr_io_queues);
}

--nr-io-queues=<NUM>, -i <NUM> ]   --- number of io queues to use (default is core count)
--nr-write-queues=<NUM>, -W <NUM> ] --- number of write queues to use (default 0)
--nr-poll-queues=<NUM>, -P <NUM> ] --- number of poll queues to use (default 0)

以 4核CPU -P 4 -i 4 -W 4 为例
总队列数为 12
HCTX_TYPE_READ = 4
HCTX_TYPE_DEFAULT = min(12-4=8, 4) = 4
HCTX_TYPE_POLL = min(12-4-4=4, 4) = 4

 arkin set.map{0}: nr_queues{4} queue_offset{0}
 arkin set.map{0}: cpu[0] hctx[0]
 arkin set.map{0}: cpu[1] hctx[1]
 arkin set.map{0}: cpu[2] hctx[2]
 arkin set.map{0}: cpu[3] hctx[3]

 arkin set.map{1}: nr_queues{4} queue_offset{4}
 arkin set.map{1}: cpu[0] hctx[4]
 arkin set.map{1}: cpu[1] hctx[5]
 arkin set.map{1}: cpu[2] hctx[6]
 arkin set.map{1}: cpu[3] hctx[7]

 arkin set.map{2}: nr_queues{4} queue_offset{8}
 arkin set.map{2}: cpu[0] hctx[8]
 arkin set.map{2}: cpu[1] hctx[9]
 arkin set.map{2}: cpu[2] hctx[10]
 arkin set.map{2}: cpu[3] hctx[11]

 arkins nvme0: queue_count{13}, sqsize{127}, nr_poll_queues{4}
 arkins nvme0: tag_set{00000000355f5f6d}
 ======================================
arkins nvme0n1
cpu{0}, ctx{00000000dc81be37} hctx[0]{00000000f0d7863b} hctx[1]{0000000091da2e1a} hctx[2]{00000000bb4ab1dd}
cpu{1}, ctx{000000008e1c04dd} hctx[0]{00000000e517b9e9} hctx[1]{0000000051c64a5a} hctx[2]{00000000bab625a6}
cpu{2}, ctx{00000000461579e0} hctx[0]{00000000dda2891a} hctx[1]{0000000065ba0f7d} hctx[2]{00000000e6ba010a}
cpu{3}, ctx{00000000c053a4a6} hctx[0]{00000000d107465d} hctx[1]{0000000051f01bc2} hctx[2]{00000000a996ecaa}

idx{0}, hctx{00000000f0d7863b} driver_data{0000000042979729}
idx{1}, hctx{00000000e517b9e9} driver_data{00000000a6795137}
idx{2}, hctx{00000000dda2891a} driver_data{000000002d44791c}
idx{3}, hctx{00000000d107465d} driver_data{000000008366a13c}
idx{4}, hctx{0000000091da2e1a} driver_data{00000000493e928a}
idx{5}, hctx{0000000051c64a5a} driver_data{0000000030690144}
idx{6}, hctx{0000000065ba0f7d} driver_data{000000005e17196d}
idx{7}, hctx{0000000051f01bc2} driver_data{000000007478fde2}
idx{8}, hctx{00000000bb4ab1dd} driver_data{0000000021b9747c}
idx{9}, hctx{00000000bab625a6} driver_data{00000000af9d2489}
idx{10}, hctx{00000000e6ba010a} driver_data{00000000de9db79d}
idx{11}, hctx{00000000a996ecaa} driver_data{000000000495437c}

======================================
arkins nvme0n2:
cpu{0}, ctx{00000000cd2412d2} hctx[0]{00000000e2b0830f} hctx[1]{000000009b64f336} hctx[2]{000000005c2086ba}
cpu{1}, ctx{0000000041a6ab8b} hctx[0]{00000000fe45f078} hctx[1]{0000000091a8c9e7} hctx[2]{0000000067919b63}
cpu{2}, ctx{00000000a0a51439} hctx[0]{00000000c663639a} hctx[1]{000000004075fb37} hctx[2]{00000000070d6988}
cpu{3}, ctx{000000000591a8a0} hctx[0]{00000000061aa23f} hctx[1]{0000000062d3ce92} hctx[2]{000000001d49a4b7}

idx{0}, hctx{00000000e2b0830f} driver_data{0000000042979729}
idx{1}, hctx{00000000fe45f078} driver_data{00000000a6795137}
idx{2}, hctx{00000000c663639a} driver_data{000000002d44791c}
idx{3}, hctx{00000000061aa23f} driver_data{000000008366a13c}
idx{4}, hctx{000000009b64f336} driver_data{00000000493e928a}
idx{5}, hctx{0000000091a8c9e7} driver_data{0000000030690144}
idx{6}, hctx{000000004075fb37} driver_data{000000005e17196d}
idx{7}, hctx{0000000062d3ce92} driver_data{000000007478fde2}
idx{8}, hctx{000000005c2086ba} driver_data{0000000021b9747c}
idx{9}, hctx{0000000067919b63} driver_data{00000000af9d2489}
idx{10}, hctx{00000000070d6988} driver_data{00000000de9db79d}
idx{11}, hctx{000000001d49a4b7} driver_data{000000000495437c}
